﻿#include "FileSys.h"
#include "PathScanner.h"
#include <ShlObj.h>
#include <Shlwapi.h>
#include <ctime>
#include "../CPP/Common/MyCom.h"
#include "../CPP/Common/MyWindows.h"


namespace SevenZip
{
namespace intl
{

class ScannerCallback : public PathScanner::Callback
{
private:

	std::vector< FilePathInfo > m_files;
	bool m_recursive;
	bool m_onlyFirst;

public:

	ScannerCallback( bool recursive, bool onlyFirst = false ) : m_recursive( recursive ), m_onlyFirst( onlyFirst ) {}
	const std::vector< FilePathInfo >& GetFiles() { return m_files; }

	virtual bool ShouldDescend( const FilePathInfo& /*directory*/ ) { return m_recursive; }
	virtual void ExamineFile( const FilePathInfo& file, bool& exit )
	{
		m_files.push_back( file );
		if ( m_onlyFirst )
		{
			exit = true;
		}
	}
};


class IsEmptyCallback : public PathScanner::Callback
{
private:

	bool m_isEmpty;

public:

	IsEmptyCallback() : m_isEmpty( true ) {}
	bool IsEmpty() const { return m_isEmpty; }

	virtual bool ShouldDescend( const FilePathInfo& /*directory*/ ) { return true; }
	virtual void ExamineFile( const FilePathInfo& /*file*/, bool& exit ) { m_isEmpty = false; exit = true; }
};


TString FileSys::GetPath( const TString& filePath )
{
	// Find the last "\" or "/" in the string and return it and everything before it.
	size_t index  = filePath.rfind( _T( '\\' ) );
	size_t index2 = filePath.rfind( _T( '/' ) );

	if ( index2 != std::string::npos && index2 > index )
	{
		index = index2;
	}

	if ( index == std::string::npos )
	{
		// No path sep.
		return TString();
	}
	else if ( index + 1 >= filePath.size() )
	{
		// Last char is path sep, the whole thing is a path.
		return filePath;
	}
	else
	{
		return filePath.substr( 0, index + 1 );
	}
}

TString FileSys::GetFileName( const TString& filePathOrName )
{
	// Find the last "\" or "/" in the string and return everything after it.
	size_t index  = filePathOrName.rfind( _T( '\\' ) );
	size_t index2 = filePathOrName.rfind( _T( '/' ) );

	if ( index2 != std::string::npos && index2 > index )
	{
		index = index2;
	}

	if ( index == std::string::npos )
	{
		// No path sep, return the whole thing.
		return filePathOrName;
	}
	else if ( index + 1 >= filePathOrName.size() )
	{
		// Last char is path sep, no filename.
		return TString();
	}
	else
	{
		return filePathOrName.substr( index + 1, filePathOrName.size() - ( index + 1 ) );
	}
}

TString FileSys::AppendPath( const TString& left, const TString& right )
{
	if ( left.empty() )
	{
		return right;
	}

	TCHAR lastChar = left[ left.size() - 1 ];
	if ( lastChar == _T( '\\' ) || lastChar == _T( '/' ) )
	{
		return left + right;
	}
	else
	{
		return left + _T( "\\" ) + right;
	}
}

TString FileSys::ExtractRelativePath( const TString& basePath, const TString& fullPath )
{
	if ( basePath.size() >= fullPath.size() )
	{
		return TString();
	}

	if ( basePath != fullPath.substr( 0, basePath.size() ) )
	{
		return TString();
	}
	return fullPath.substr( basePath.size(), fullPath.size() - basePath.size() );
}

bool FileSys::DirectoryExists( const TString& path )
{
	DWORD attributes = GetFileAttributes( path.c_str() );

	if ( attributes == INVALID_FILE_ATTRIBUTES )
	{
		return false;
	}
	else
	{
		return ( attributes & FILE_ATTRIBUTE_DIRECTORY ) != 0;
	}
}


bool FileSys::FileExists(const TString& path)
{
    DWORD attributes = GetFileAttributes(path.c_str());

    if (attributes == INVALID_FILE_ATTRIBUTES)
    {
        return false;
    }
    else
    {
        return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
    }
}


bool FileSys::PathExists(const TString& path)
{
    DWORD attributes = GetFileAttributes(path.c_str());
    return attributes != INVALID_FILE_ATTRIBUTES;
}



bool FileSys::RemovePath(const TString& path)
{
    if (path.empty() || path.length() > MAX_PATH)
    {
        return false;
    }

    //确保文件或者目录存在
    if (!PathExists(path))
        return true;

    //目录的路径以2个\0结尾
    TString tmp_path = path;
    tmp_path.resize(tmp_path.size() + 2);

    SHFILEOPSTRUCT FileOp;
    ZeroMemory(&FileOp, sizeof(SHFILEOPSTRUCT));
    FileOp.fFlags |= FOF_SILENT;        /*不显示进度*/
    FileOp.fFlags |= FOF_NOERRORUI;        /*不报告错误信息*/
    FileOp.fFlags |= FOF_NOCONFIRMATION;/*直接删除，不进行确认*/
    FileOp.fFlags &= ~FOF_ALLOWUNDO; /*直接删除，不放入回收站*/
    FileOp.hNameMappings = NULL;
    FileOp.hwnd = NULL;
    FileOp.lpszProgressTitle = NULL;
    FileOp.wFunc = FO_DELETE;
    FileOp.pFrom = &tmp_path[0]; /*要删除的目录，必须以2个\0结尾*/
    FileOp.pTo = NULL;

    /*删除目录*/
    if (0 == SHFileOperation(&FileOp))
    {
        return true;
    }
    else
    {
        return false;
    }
}


bool FileSys::RenameFile(const TString& oldfile, const TString&newfile)
{
    return MoveFileEx(oldfile.c_str(), newfile.c_str(), MOVEFILE_REPLACE_EXISTING) != FALSE;
}


bool FileSys::BackupFile(const TString&orignal, const TString&backup)
{
    return MoveFileEx(orignal.c_str(), backup.c_str(), MOVEFILE_REPLACE_EXISTING) != FALSE
        || BackupFile(orignal.c_str(), backup.c_str()) != FALSE;
}

static std::wstring path_without_extension(const std::wstring& lpszOriginalPath)
{
    size_t pos_base_name = lpszOriginalPath.find_last_of(L"/\\");
    size_t pos_dot = lpszOriginalPath.find(L'.', pos_base_name);
    return lpszOriginalPath.substr(0, pos_dot);
}

static std::wstring path_without_extension(const std::wstring& lpszOriginalPath, std::wstring& ext)
{
    size_t pos_base_name = lpszOriginalPath.find_last_of(L"/\\");
    size_t pos_dot = lpszOriginalPath.find(L'.', pos_base_name);
    ext =
        (pos_dot != std::wstring::npos)
        ? lpszOriginalPath.substr(pos_dot) : L"";

    return lpszOriginalPath.substr(0, pos_dot);
}


static std::wstring get_date_time_str()
{
    time_t rawtime;
    struct tm * timeinfo;
    wchar_t buffer[80] = { 0 };
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    wcsftime(buffer, 80, L"%Y%d%m-%H%M%S", timeinfo);
    return  buffer;
}
static std::wstring get_unique_path_with_dt(const std::wstring&path)
{
    std::wstring ext;
    std::wstring base_name = path_without_extension(path, ext);

    // 找第一个不存在的路径
    std::wstring save_path = path;
    while (FileSys::PathExists(save_path))
    {
        save_path = base_name + L'~' + get_date_time_str() + ext;
    }
    return save_path;
}

TString FileSys::GetUniquePath(const TString& path)
{
    std::wstring ext;
    std::wstring base_name = path_without_extension(path, ext);

    // 找第一个不存在的路径
    std::wstring save_path = path;
    while (PathExists(save_path))
    {
        save_path = base_name + L'~' + get_date_time_str() + ext;
    }
    return save_path;
}

bool FileSys::IsDirectoryEmptyRecursive(const TString& path)
{
	IsEmptyCallback cb;
	PathScanner::Scan( path, cb );
	return cb.IsEmpty();
}

bool FileSys::CreateDirectoryTree( const TString& path )
{
	int ret = SHCreateDirectoryEx( NULL, path.c_str(), NULL );
    return ret == ERROR_SUCCESS || ret == ERROR_ALREADY_EXISTS;
}

std::vector< FilePathInfo > FileSys::GetFile( const TString& filePathOrName )
{
	TString path = FileSys::GetPath( filePathOrName );
	TString name = FileSys::GetFileName( filePathOrName );

	ScannerCallback cb( false, true );
	PathScanner::Scan( path, name, cb );
	return cb.GetFiles();
}

std::vector< FilePathInfo > FileSys::GetFilesInDirectory( const TString& directory, const TString& searchPattern, bool recursive )
{
	ScannerCallback cb( recursive );
	PathScanner::Scan( directory, searchPattern, cb );
	return cb.GetFiles();
}

CMyComPtr< IStream > FileSys::OpenFileToRead( const TString& filePath )
{
	CMyComPtr< IStream > fileStream;

#ifdef _UNICODE
	const WCHAR* filePathStr = filePath.c_str();
#else
	WCHAR filePathStr[MAX_PATH];
	MultiByteToWideChar( CP_UTF8, 0, filePath.c_str(), filePath.length() + 1, filePathStr, MAX_PATH );
#endif
	if ( FAILED( SHCreateStreamOnFileEx( filePathStr, STGM_READ, FILE_ATTRIBUTE_NORMAL, FALSE, NULL, &fileStream ) ) )
	{
		return NULL;
	}

	return fileStream;
}

CMyComPtr< IStream > FileSys::OpenFileToWrite( const TString& filePath )
{
	CMyComPtr< IStream > fileStream;
	
#ifdef _UNICODE
	const WCHAR* filePathStr = filePath.c_str();
#else
	WCHAR filePathStr[MAX_PATH];
	MultiByteToWideChar( CP_UTF8, 0, filePath.c_str(), filePath.length() + 1, filePathStr, MAX_PATH );
#endif
	if ( FAILED( SHCreateStreamOnFileEx( filePathStr, STGM_CREATE | STGM_WRITE, FILE_ATTRIBUTE_NORMAL, TRUE, NULL, &fileStream ) ) )
	{
		return NULL;
	}

	return fileStream;
}

}
}
